package io.jsen.meta.diff.support;

import com.google.common.collect.Maps;
import io.jsen.meta.diff.annotation.MetaConvert;
import io.jsen.meta.diff.annotation.MetaFieldAnnotation;
import io.jsen.meta.diff.annotation.empty.IgnoreConvert;
import io.jsen.meta.diff.annotation.empty.IgnoreIndexFeature;
import io.jsen.meta.diff.feature.IndexFeature;
import io.jsen.meta.diff.field.MetaField;
import io.jsen.meta.diff.field.MetaFieldDefine;
import io.jsen.meta.diff.obj.Convert;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Map;

/**
 * @author jsen
 * @date 2021/4/13
 */
@SuppressWarnings("all")
public class MetaFieldDefineSupport {

    private static final Map<Field, Consumer<MetaConvert, CoConvert>> CONVERT_CACHE = Maps.newHashMap();
    private static final Map<Field, Consumer<MetaFieldAnnotation, CoAnnotation>> FIELD_ANNOTATION = Maps.newHashMap();

    public static CoConvert getConvert(MetaField metaField) throws IllegalAccessException, InstantiationException {
        if (metaField == null || metaField.getFieldDefine() == null ||
                metaField.getFieldDefine().getField() == null) {
            return null;
        }
        Field field = metaField.getFieldDefine().getField();
        if (CONVERT_CACHE.containsKey(field)) {
            return CONVERT_CACHE.get(field).getHandler();
        }
        MetaConvert metaConvert = field.getAnnotation(MetaConvert.class);
        if (metaConvert == null) {
            CONVERT_CACHE.put(field, new Consumer<>(null, null));
            return null;
        } else {
            Consumer<MetaConvert, CoConvert> consumer = new Consumer<>(metaConvert,
                    new CoConvert(metaConvert.nativeConvert().newInstance(),
                            metaConvert.metaConvert().newInstance()));
            CONVERT_CACHE.put(field, consumer);
            return consumer.getHandler();
        }
    }

    public static CoAnnotation getMetaFieldAnnotation(MetaFieldDefine metaFieldDefine) {
        if (metaFieldDefine == null || metaFieldDefine.getField() == null) {
            return null;
        }
        Field field = metaFieldDefine.getField();
        if (FIELD_ANNOTATION.containsKey(field)) {
            return FIELD_ANNOTATION.get(field).getHandler();
        }
        MetaFieldAnnotation annotation = field.getAnnotation(MetaFieldAnnotation.class);
        if (annotation == null) {
            Consumer<MetaFieldAnnotation, CoAnnotation> consumer = new Consumer<>(null, null);
            FIELD_ANNOTATION.put(field, consumer);
            return null;
        } else {
            Consumer<MetaFieldAnnotation, CoAnnotation> consumer = new Consumer<>(annotation,
                    new CoAnnotation(annotation));
            FIELD_ANNOTATION.put(field, consumer);
            return consumer.getHandler();
        }
    }

    @Data
    @AllArgsConstructor
    private static class Consumer<T extends Annotation, H> {
        private T annotation;
        private H handler;
    }

    @AllArgsConstructor
    public static class CoConvert {
        private final Convert nativeConvert;
        private final Convert metaConvert;

        public Convert getNative() {
            if (nativeConvert != null && !(nativeConvert instanceof IgnoreConvert)) {
                return nativeConvert;
            }
            return null;
        }

        public Convert getMeta() {
            if (metaConvert != null && !(metaConvert instanceof IgnoreConvert)) {
                return metaConvert;
            }
            return null;
        }
    }

    @Data
    @AllArgsConstructor
    public static class CoAnnotation {
        private MetaFieldAnnotation metaFieldAnnotation;

        public Class<? extends IndexFeature> getIndexFeature() {
            if (metaFieldAnnotation.indexFeatures() == IgnoreIndexFeature.class) {
                return null;
            }
            return metaFieldAnnotation.indexFeatures();
        }
    }

    private MetaFieldDefineSupport() {
    }
}
